C から Rust のコードを呼び出す
基本的には「Rust から C のコードを呼び出す」場合と同じように定義することで、呼び出すことが可能 関数には extern "C" を付ける
Rust のシンボルはデフォルトで マングリング されるので、関数定義にも #[no_mangle] を付ける したがって、他のすべてのシンボルと衝突する可能性があるため、「C に対する公開するシンボル名には 接頭辞 を付けることを検討しよう」 データ構造定義には #[repr(C)] を付けて、メモリレイアウト を C 互換にする warning.icon 特に注意すべきポイント
そのため、操作するには 参照 に変換する必要がある code:rs
pub extern "C" fn add_contents(p: *const FfiStruct) -> u32 {
// C から与えられた生ポインタを Rust の参照に変換
let s: &FfiStruct = unsafe { &*p };
s.integer + s.byte as u32;
}
しかし、上記の定義だと NULL を渡すことができてしまう
code:c
uint32_t result = add_contents(NULL);
このように、Rust の参照のもつ仮定(不変条件)と保証に従うかをチェックするのはプログラマの役割 である code:rs
pub extern "C" fn add_contents(p: *const FfiStruct) -> u32 {
// C から与えられた生ポインタを Rust の参照に変換
let s = match unsafe { p.as_ref() } {
Some(r) => r,
None => return 0,
};
s.integer + s.byte as u32;
}
しかし、生ポインタでは起こりうる
code:rs
impl FfiStruct {
pub fn new(v: u32) -> Self {
Self { byte: 0, integer: v }
}
}
pub extern "C" fn new_struct(v: u32) -> *mut FfiStruct {
let mut s = FfiStruct::new(v);
&mut s // 関数から抜けると無効になるスタックオブジェクトへの生ポインタを返す
}
そのため、生ポインタは スタック ではなく ヒープ のメモリを参照すべきである しかし、単に Box を用いるだけでは実現できない
code:rs
pub extern "C" fn new_struct(v: u32) -> *mut FfiStruct {
let mut s = FfiStruct::new(v);
let mut b = Box::new(s); // FFiStruct をヒープに移動する
&mut *b
}
∵ 所有 している Box はスタック上に存在する ため、関数が終了するとヒープメモリも解放するため After calling this function, the caller is responsible for the memory previously managed by the Box.
code:rs
pub extern "C" fn new_struct_heap(v: u32) -> *mut FfiStruct {
let mut s = FfiStruct::new(v);
let mut b = Box::new(s);
Box::into(b)
}
これにより、ヒープメモリを解放することが可能となる
code:rs
pub extern "C" fn free_struct_raw(p: *mut FfiStruct) {
if p.is_null() {
return;
}
let _b = unsafe {
Box::from_raw(p)
};
}
warning.icon C が誤って同じポインタを複数回解放した( free_struct_raw)場合は 二重解放 となり、 未定義動作 を引き起こす これは Rust では防げない
「panic! は 未定義動作 となるので、通過しないようにしよう」 C もアンワインドでスタックが巻き戻るのを想定してないし、Rust 自身もパニックが FFI 境界を超えて伝播すること想定していない